# -*- coding:utf-8 -*-



from pygame.locals import *
import pygame
import time
import random


class Rectangle:
    """定义一个矩形"""
    def __init__(self, pos, size, factor):
        """
        根据图片原点位置和图片尺寸初始化一个矩形
        :param pos: 原点位置，二元组，(x轴值, y轴值)
        :param size: 尺寸，二元组，(图片长, 图片宽)
        :param factor: 碰撞系数，0-1的小数，碰撞体积等比缩小
        """
        rate = (1 - factor) / 2
        self.left = pos[0] + size[0] * rate
        self.right = self.left + size[0] * factor
        self.top = pos[1] + size[0] * rate
        self.bottom = self.top + size[1] * factor


def est_collisions(t1, t2):
    """
    判断2个矩形是否碰撞
    :param t1: 第一个矩形
    :param t2: 第二个矩形
    :return: bool type
    """
    if t1.right < t2.left or t1.left > t2.right or t1.bottom < t2.top or t1.top > t2.bottom:
        return False
    else:
        return True


class Flyer:
    """飞行物基类"""
    def __init__(self, screen):
        self.x = 0
        self.y = 0
        self.screen = screen
        self.image = None

    def display(self):
        """绘制飞行物到窗口中"""
        self.screen.blit(self.image, (self.x, self.y))


class Plane(Flyer):
    """飞机类，继承自飞行物类"""
    def __init__(self, screen):
        super(Plane, self).__init__(screen)
        self.bullet_list = []    # 存储发射出去的子弹对象引用
        self.start_time = time.time()   # 计时器，用于调整子弹发射频率
        self._explore_animation_num = 0    # 计数器，用于调整飞机爆炸的动画
        self.is_hero = False     # 标识玩家飞机
        self.collision_factor = 0.7     # 碰撞系数

    def explore(self, frame_rate, animation_length=0.7):
        """
        飞机碰撞后爆炸，根据游戏主窗口的帧率以及爆炸动画时长加载某一张爆炸图片
        :param frame_rate: 帧率
        :param animation_length: 动画播放时长，单位秒
        :return: bool type，告诉外部调用者当前飞机是否结束爆炸
        """
        frame_nums = int(animation_length / 4 / frame_rate)    # 计算单个图片需要播放的帧数
        self._explore_animation_num += 1
        if 1 <= self._explore_animation_num < 1 + frame_nums:
            self.image = pygame.image.load("./images/boom1.png")
        elif 1 + frame_nums <= self._explore_animation_num < 1 + 2 * frame_nums:
            self.image = pygame.image.load("./images/boom2.png")
        elif 1 + 2 * frame_nums <= self._explore_animation_num < 1 + 3 * frame_nums:
            self.image = pygame.image.load("./images/boom3.png")
        elif 1 + 3 * frame_nums <= self._explore_animation_num < 1 + 4 * frame_nums:
            self.image = pygame.image.load("./images/boom4.png")
        elif self._explore_animation_num == 1 + 4 * frame_nums:
            self.image = pygame.image.load("./images/Invisible.png")    # 让飞机变透明
            self._explore_animation_num = 0     # 重新将计时器清零
            return True     # 飞机爆炸动画播放完成，返回True
        self.display()
        return False


class Bullet(Flyer):
    """子弹类，继承自飞行物类"""
    def __init__(self, screen, x, y):
        super(Bullet, self).__init__(screen)
        self.x = x
        self.y = y


class MyPlane(Plane):
    """玩家飞机，继承自飞机类"""
    def __init__(self, screen):
        super(MyPlane, self).__init__(screen)
        self.x = 200
        self.y = 400
        self.image = pygame.image.load("./images/Heros.png").subsurface(pygame.Rect(393, 102, 116, 93))
        self.is_hero = True
        self.collision_factor = 0.5     # 将玩家碰撞系数将为0.5，降低难度

    def ret_rectangle(self):
        """获取碰撞体积"""
        pos = (self.x, self.y)
        size = (124, 92)
        rectangle = Rectangle(pos, size, self.collision_factor)
        return rectangle

    def move(self, direction, step_size=10):
        """
        飞机移动，会检测越界，如果达到边界则保持现有位置无法越界
        :param direction: 方向，上下左右4个方向
        :param step_size: 步长，每一帧移动的像素点数目
        :return:
        """
        # 判断本机是否越界
        if direction == "left":
            self.x -= step_size
            if self.x < 0:
                self.x = 0
        elif direction == "right":
            self.x += step_size
            if self.x > 400:
                self.x = 400
        elif direction == "up":
            self.y -= step_size
            if self.y < 0:
                self.y = 0
        elif direction == "down":
            self.y += step_size
            if self.y > 470:
                self.y = 470

    def collisions(self, e_list, eb_list):
        """
        遍历敌机的子弹和敌机，并做碰撞检测，如果有碰撞则移除子弹或敌机，并返回True
        :param e_list: 敌机列表
        :param eb_list: 敌机子弹列表
        :return: 发生碰撞则返回True
        """
        for eb in eb_list:
            if est_collisions(self.ret_rectangle(), eb.ret_rectangle()):
                eb_list.remove(eb)
                return True
        for e in e_list:
            if est_collisions(self.ret_rectangle(), e.ret_rectangle()):
                e_list.remove(e)
                return True
        return False

    def fire(self, frequency=0.2):
        """
        发射子弹，如果当前时间比计时器的初始时间大frequency秒才会将子弹加入弹夹，这样控制发射子弹频率
        :param frequency: 发射子弹间隔，提高用户体验，时间秒
        :return:
        """
        current_time = time.time()
        if current_time - self.start_time > frequency:
            MyBullet(self.screen, self.x, self.y)
            self.start_time = current_time


class EnemyPlane(Plane):
    """敌机的类，继承自飞机类"""
    def __init__(self, screen):
        super(EnemyPlane, self).__init__(screen)
        self.x = random.choice(range(408))
        self.y = -80
        self.plane_type = "e{0}".format(random.choice(range(3)))        # 利用随机函数随机选取敌机图片并加载
        self.image = pygame.image.load("./images/{0}.png".format(self.plane_type))

    def ret_rectangle(self):
        """获取碰撞体积"""
        pos = (self.x, self.y)
        size = (0, 0)
        if self.plane_type == "e0":
            size = (116, 82)
        elif self.plane_type == "e1":
            size = (98, 76)
        if self.plane_type == "e2":
            size = (104, 74)
        rectangle = Rectangle(pos, size, self.collision_factor)
        return rectangle

    def move(self):
        self.y += 2
        # 判断敌机是否越界
        if self.y > 550:
            return True

    def collisions(self, mb_list):
        """
        遍历本机的子弹，做碰撞检测，如果有碰撞则从本机子弹列表中移除子弹，并返回True
        :param mb_list: 本机子弹列表
        :return: 发生碰撞则返回True
        """
        for mb in mb_list:
            if est_collisions(self.ret_rectangle(), mb.ret_rectangle()):
                mb_list.remove(mb)
                return True

    def fire(self, frequency=1.0):
        """
        发射子弹，如果当前时间比计时器的初始时间大frequency秒才会将子弹加入弹夹，这样控制发射子弹频率
        :param frequency: 发射子弹间隔，提高用户体验，时间秒
        :return:
        """
        current_time = time.time()
        if current_time - self.start_time > frequency:
            EnemyBullet(self.screen, self.plane_type, self.x, self.y)
            self.start_time = current_time


class MyBullet(Bullet):
    """玩家子弹类，继承自子弹类"""
    bullet_list = []    # 子弹列表

    def __init__(self, screen, x, y):
        super(MyBullet, self).__init__(screen, x, y)
        self.x = x + 55     # 从玩家飞机机头发射而不是左上角原点
        self.y = y
        self.image = pygame.image.load("./images/pd.png")
        self.collision_factor = 1.0
        MyBullet.bullet_list.append(self)       # 将新生成的子弹加入子弹列表

    def ret_rectangle(self):
        pos = (self.x, self.y)
        size = (3, 17)
        rectangle = Rectangle(pos, size, self.collision_factor)
        return rectangle

    def move(self):
        self.y -= 10
        if self.y < -20:
            # 越界则从本机子弹列表中移除本实例对象，并返回True
            MyBullet.bullet_list.remove(self)
            return True


class EnemyBullet(Bullet):
    """敌人子弹类，继承自子弹类"""
    bullet_list = []  # 子弹列表

    def __init__(self, screen, plane_type, x, y):
        super(EnemyBullet, self).__init__(screen, x, y)
        # 因为敌机类型不同，图片大小不同，所以机头位置不一样，需要分开处理
        if plane_type == "e0":
            self.x = x + 53
            self.y = y + 82
        elif plane_type == "e1":
            self.x = x + 45
            self.y = y + 76
        elif plane_type == "e2":
            self.x = x + 45
            self.y = y + 74
        self.image = pygame.image.load("./images/enemy_bullet.png")
        self.collision_factor = 0.7
        EnemyBullet.bullet_list.append(self)  # 将新生成的子弹加入子弹列表

    def ret_rectangle(self):
        pos = (self.x, self.y)
        size = (10, 20)
        rectangle = Rectangle(pos, size, self.collision_factor)
        return rectangle

    def move(self):
        self.y += 4
        if self.y > 570:
            # 越界则从敌机子弹列表中移除本实例对象，并返回True
            EnemyBullet.bullet_list.remove(self)
            return True


def key_move(my_plane):
    """
    键盘控制函数，不应该把碰撞检测放到move方法里
    不然不移动的时候撞到子弹就不会检测碰撞
    """
    # 获取事件，比如按键等
    for event in pygame.event.get():
        # 判断是否是点击了退出按钮
        if event.type == QUIT:
            print("退出游戏~")
            exit()
    # 获取按下的键(返回的是元组值)
    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_a] or pressed_keys[K_LEFT]:
        my_plane.move("left")
    elif pressed_keys[K_d] or pressed_keys[K_RIGHT]:
        my_plane.move("right")
    elif pressed_keys[K_w] or pressed_keys[K_UP]:
        my_plane.move("up")
    elif pressed_keys[K_s] or pressed_keys[K_DOWN]:
        my_plane.move("down")


def key_fire(my_plane):
    """
    把key_move和key_fire分开写，不然放在同一个函数里会出现卡键盘的情况：
    当同时按下方向键和空格的时候不会同时生效，在PC上用键盘玩过街机模拟器
    游戏特别是KOF、街霸的同学一定深有同感
    """
    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_SPACE]:
        my_plane.fire()


def main():
    """游戏的主程序执行函数"""
    animation_frames = 30
    frame_rate = 1 / animation_frames

    # 创建窗口:set_mode(分辨率=(0,0),标志=0,深度=0)
    screen = pygame.display.set_mode((512, 568), 0, 0)

    # 创建一个游戏背景图片(512*1536)
    background = pygame.image.load("./images/bg2.png")
    m = -968   # 初始化游戏背景图片标轴y的值

    # 创建一个玩家飞机对象
    my_plane = MyPlane(screen)

    # 定义用于存放敌机列表
    enemy_list = []
    # 爆炸列表
    explore_list = []

    # 定义用于存放敌机子弹的列表，额外定义这个变量可以保证当敌机被击落后子弹依然存在
    while True:
        # 绘制位图
        screen.blit(background, (0, m))
        m += 2
        if m >= -200:
            m = -968

        # 显示玩家飞机
        my_plane.display()
        # 键盘控制（负责移动玩家），并与敌机子弹碰撞检测
        key_move(my_plane)
        key_fire(my_plane)
        if my_plane.collisions(enemy_list, EnemyBullet.bullet_list):
            explore_list.append(my_plane)
            # print("玩家被击中，游戏结束~")
            # break

        # 随机输出敌机
        if random.choice(range(60)) == 10:
            enemy_list.append(EnemyPlane(screen))

        # 遍历所有敌机，显示敌机，移动敌机，并与玩家子弹碰撞检测，如果发生碰撞，加入碰撞列表
        for em in enemy_list:
            em.display()
            em.move()
            em.fire()
            if em.collisions(MyBullet.bullet_list):
                explore_list.append(em)
                enemy_list.remove(em)

        # 遍历本机和敌机的类变量bullet_list，显示子弹并移动子弹
        for mb in MyBullet.bullet_list:
            mb.display()
            mb.move()
        for eb in EnemyBullet.bullet_list:
            eb.display()
            eb.move()

        # 遍历爆炸列表，播放爆炸动画
        for plane in explore_list:
            if plane.explore(frame_rate):
                explore_list.remove(plane)
                if plane.is_hero is True:
                    print("您已被击毁，游戏关闭~")
                    return

        # 更新屏幕显示
        pygame.display.update()

        # 定时睡眠（时钟）
        time.sleep(frame_rate)


# 判断当前是否是主程序，若是就执行主程序。
if __name__ == "__main__":
    main()
